﻿Imports TagLib
Imports System.Runtime.CompilerServices

''' <summary>
''' 用于读取文件ID3v1/ID3v2/APEtag标签，是对TagLib的封装
''' </summary>
''' <remarks>实验性的增加对CUE Sheet的支持，格式为"Path,#"指CUE表里的第#首曲目</remarks>
Public Class TagReader
    Implements IDisposable

    Protected Friend _file As File
    Protected Friend ID3v1 As Id3v1.Tag = Nothing
    Protected Friend ID3v2 As Id3v2.Tag = Nothing
    Protected Friend APEtag As Ape.Tag = Nothing
    Protected Friend Oggtag As Ogg.XiphComment = Nothing
    Protected Friend Mpeg4tag As Mpeg4.AppleTag = Nothing
    Protected Friend FLACtag As Flac.Metadata = Nothing
    Protected Friend SpecialTag As Tag
#Region "New"
    ''' <summary>
    ''' 通过文件来创建类的实例
    ''' </summary>
    ''' <param name="filePath">要读取的文件路径</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal filePath As String)
        Dim reg As New System.Text.RegularExpressions.Regex("(?<Path>.+?\.[Cc][Uu][Ee]),(?<Number>\d+)$")
        Dim mat = reg.Match(filePath)
        If mat IsNot Nothing AndAlso Not String.IsNullOrEmpty(mat.Value) Then

            Return
        End If
        _file = File.Create(filePath, ReadStyle.Average)
        _file.Mode = File.AccessMode.Read
        ID3v1 = _file.GetTag(TagTypes.Id3v1)
        ID3v2 = _file.GetTag(TagTypes.Id3v2)
        APEtag = _file.GetTag(TagTypes.Ape)
        Dim suffix = IO.Path.GetExtension(filePath)
        If suffix.Equals(".ogg") Then
            Oggtag = _file.GetTag(TagTypes.Xiph)
            SpecialTag = Oggtag
            Exit Sub
        End If
        If suffix.Equals(".mp4") Or suffix.Equals(".m4a") Then
            Mpeg4tag = _file.GetTag(TagTypes.Apple)
            SpecialTag = Mpeg4tag
            Exit Sub
        End If
        If suffix.Equals(".flac") Then
            FLACtag = _file.GetTag(TagTypes.FlacMetadata)
            SpecialTag = FLACtag
            Exit Sub
        End If
        If suffix.Equals(".wma") Then
            SpecialTag = _file.GetTag(TagTypes.Asf)
        End If
        If suffix.Equals(".cda") Then
            SpecialTag = _file.GetTag(TagTypes.MovieId)
        End If
    End Sub
    ''' <summary>
    ''' 通过文件来创建类的实例并设置读取标签的优先级
    ''' </summary>
    ''' <param name="filepath">要读取的文件路径</param>
    ''' <param name="Priority">读取标签的优先级，默认为0123，即ID3v2&gt;ID3v1&gt;APEtag&gt;Specialtag</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal filepath As String, ByVal Priority As Integer)
        Me.New(filepath)
        Me.TagPriority = Priority
    End Sub
    ''' <summary>
    ''' 通过文件来创建类的实例并设置读取标签的优先级
    ''' </summary>
    ''' <param name="filepath">要读取的文件路径</param>
    ''' <param name="Priority">读取标签的优先级，默认为{0,1,2,3}，即ID3v2&gt;ID3v1&gt;APEtag&gt;Specialtag</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal filepath As String, ByVal Priority As Integer())
        Me.New(filepath)
        Me.Priority = Priority
    End Sub
#End Region
    Private Priority As Integer() = {0, 1, 2, 3}
    ''' <summary>
    ''' 设置读取标签的优先级
    ''' </summary>
    ''' <value>读取标签的优先级，默认为0123，即ID3v2&gt;ID3v1&gt;APEtag&gt;Specialtag</value>
    ''' <returns>读取标签的优先级</returns>
    ''' <remarks></remarks>
    Public Property TagPriority As Integer
        Get
            Return Priority.IntArrToInt
        End Get
        Set(ByVal value As Integer)
            Priority = value.IntToIntArr
        End Set
    End Property
    ''' <summary>
    ''' 用于优先级，分别返回各标签
    ''' </summary>
    ''' <param name="index">标签代号
    ''' <list type="number">
    ''' <item><description>0:ID3v1 </description></item>
    ''' <item><description>1:ID3v2 </description></item>
    ''' <item><description>2:APEtag </description></item>
    ''' <item><description>3:文件特定标签 </description></item>
    ''' </list></param>
    ''' <returns>返回所请求的标签类</returns>
    ''' <remarks></remarks>
    Protected Function GetTag(ByVal index As Integer) As Tag
        Select Case index
            Case 0
                Return Me.ID3v2
            Case 1
                Return Me.ID3v1
            Case 2
                Return Me.APEtag
            Case 3
                Return SpecialTag
            Case Else
                Return Nothing
        End Select
    End Function
#Region "封装内容"

    Public Function GetAlbum() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Album
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetArtist() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Performers.ArrayToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetTitle() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Title
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetYear() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Year.ToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetTrack() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Track.ToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetTrackCount() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.TrackCount.ToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetAlbumArtist() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.AlbumArtists.ArrayToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetLyric() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Lyrics
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetGenres() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Genres.ArrayToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetComposers() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Composers.ArrayToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetCopyright() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Copyright
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetConductor() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Conductor
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetDiscCount() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.DiscCount.ToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetDisc() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Disc.ToString
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetComment() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.Comment
            End If
        Next
        Return String.Empty
    End Function

    Public Function GetBPM() As String
        Dim t As Tag
        For i = 0 To 3
            t = GetTag(Priority(i))
            If t IsNot Nothing Then
                Return t.BeatsPerMinute.ToString
            End If
        Next
        Return String.Empty
    End Function
#End Region

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 检测冗余的调用

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: 释放托管状态(托管对象)。
                _file.Dispose()
            End If

            ' TODO: 释放非托管资源(非托管对象)并重写下面的 Finalize()。
            ' TODO: 将大型字段设置为 null。
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: 仅当上面的 Dispose(ByVal disposing As Boolean)具有释放非托管资源的代码时重写 Finalize()。
    'Protected Overrides Sub Finalize()
    '    ' 不要更改此代码。请将清理代码放入上面的 Dispose(ByVal disposing As Boolean)中。
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Visual Basic 添加此代码是为了正确实现可处置模式。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' 不要更改此代码。请将清理代码放入上面的 Dispose(ByVal disposing As Boolean)中。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

Module ExtendedFunc
    <Extension()> Function ArrayToString(ByVal arr As String(), Optional ByVal spliter As String = ",") As String
        If arr.Length = 0 Then Return String.Empty
        Dim result As New System.Text.StringBuilder
        For i = 0 To arr.Length - 2
            result.Append(arr(i))
            result.Append(spliter)
        Next
        result.Append(arr.Last)
        Return result.ToString
    End Function

    <Extension()> Function IntArrToInt(ByVal arr As Integer()) As Integer
        Return arr(0) * 1000 + arr(1) * 100 + arr(2) * 10 + arr(3)
    End Function

    <Extension()> Function IntToIntArr(ByVal int As Integer) As Integer()
        Dim r(3) As Integer
        r(0) = int \ 1000
        r(1) = (int \ 100) Mod 10
        r(2) = (int \ 10) Mod 10
        r(3) = int Mod 10
        Return r
    End Function
End Module